﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using SenetExperimentsRunner.Logic.Experiment;
using SenetExperimentsRunner.Logic.Runner;
using SenetExperimentsRunner.Logic.Genetic_Algorithm;

namespace SenetExperimentsRunner.GUI
{
    public partial class ExperimentRunnerUI : Form
    {
        #region /// Constants ///

        public readonly string[] STATIC_COLUMN_NAMES = {
                                                            "ID",
                                                            "Name",
                                                            "Trials",
                                                            "Player Turn Num",
                                                            "Seconds Per Turn",
                                                            "Sticks",
                                                            "Squares",
                                                            "Initial Pieces",
                                                            "Additional Safe Sq.",
                                                            "Additional Rebirth Sq.",
                                                            "P1-Type",
                                                            "P1-H-p1",
                                                            "P1-H-p2",
                                                            "P2-Type",
                                                            "P2-H-p1",
                                                            "P2-H-p2",
                                                            "Res %P1 win",
                                                            "Res %P2 win"
                                                       };

        #endregion

        private ExperimentList m_ExperimentList;
        private delegate void ProgressBarSetValueDelegate(ProgressBar progressBar, int val);
        private delegate void SetContorlStatusDelegate(Control control, bool status);

        #region /// Public Methods ///

        public ExperimentRunnerUI()
        {
            InitializeComponent();
            FormInit();
        }

        #endregion

        #region /// Private Methods ///

        private void FormInit()
        {
            // set the grid view data source
            m_ExperimentList = new ExperimentList(this);
            m_ExperimentsGrid.DataSource = m_ExperimentList;

            // set column names
            if (STATIC_COLUMN_NAMES.Length == m_ExperimentsGrid.Columns.Count)
            {
                for (int i = 0; i < STATIC_COLUMN_NAMES.Length; i++ )
                {
                    m_ExperimentsGrid.Columns[i].Name = STATIC_COLUMN_NAMES[i];
                    m_ExperimentsGrid.Columns[i].HeaderText = STATIC_COLUMN_NAMES[i];
                }    
            }

            m_ParamToChange.DataSource = Enum.GetValues(typeof (Experiment.ChangableVarEnum));

            // register to events
            ExperimentRunner.m_CurExpCompleteEvent += this.PercentageCompleteCurExp;
            ExperimentRunner.m_TotalExpCompleteEvent += this.PercentageCompleteTotalExp;
       }

        #endregion
        
        private void ProgressBarSetValueHandler(ProgressBar progressBar, int val)
        {
            progressBar.Value = val;
        }

        private void SetControlStatusHandler(Control control, bool status)
        {
            control.Enabled = status;
        }

        public void m_CreateNewExpButton_Click(object sender, System.EventArgs e)
        {
            m_PropertyGrid.SelectedObject = new Experiment();
        }

        private void m_AddToListButton_Click(object sender, EventArgs e)
        {
            if (m_SimpleExperiment.Checked)
            {
                Experiment experiment = GetClonedExperimentGrid();
                if (experiment != null)
                {
                    m_ExperimentList.Add(experiment);
                }
            }
            else if (m_ComplexExperiment.Checked)
            {
                AddComplexExperiment();
            }
        }

        private Experiment GetClonedExperimentGrid()
        {
            Experiment retExperiment = null;
            Experiment experiment = m_PropertyGrid.SelectedObject as Experiment;
            if (experiment != null)
            {
                retExperiment = (Experiment) experiment.Clone();
            }
            return retExperiment;
        }

        private void AddComplexExperiment()
        {
            // get the values needed to create the experiments
            try
            {
                double initalValue = Double.Parse(m_InitialValue.Text);
                double finalValue = Double.Parse(m_FinalValue.Text);
                double increment = Double.Parse(m_Increment.Text);
                Experiment.ChangableVarEnum variableToChange = (Experiment.ChangableVarEnum)m_ParamToChange.SelectedItem;
                Experiment experiment = GetClonedExperimentGrid();
                if (experiment == null)
                {
                    throw new NullReferenceException("unable to retreive the experiemnt from the gridview");
                }

                Object[] paramsObj = {initalValue, finalValue, increment, variableToChange, experiment};

                ParameterizedThreadStart threadStart = AddComplexExperimentAux;
                Thread thread = new Thread(threadStart);
                thread.Name = "CreateComplexExp";
                m_ToolStripProgressBar.Value = 0;
                thread.Start(paramsObj);

                //AddComplexExperiment(initalValue, finalValue, increment, variableToChange, experiment);
            }
            catch (Exception exception)
            {
                string error = String.Format(
                    "Error creating the complex experiment\nCause:{0}\n\n{1}",
                    exception.Message,
                              exception.StackTrace);
                MessageBox.Show(this, error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }

        private void AddComplexExperimentAux(object val)
        {
            object[] vals = (object[]) val;
            AddComplexExperiment((double) vals[0], (double) vals[1], (double) vals[2], (Experiment.ChangableVarEnum) vals[3], (Experiment) vals[4]);
        }

        private void AddComplexExperiment(
            double initalValue,
            double finalValue,
            double increment,
            Experiment.ChangableVarEnum variableToChange,
            Experiment experiment)
        {
            bool isDecreasing = (initalValue > finalValue);
            int totalValues = (int)(Math.Abs(initalValue - finalValue)/increment);
            for (double i = initalValue, j = 0; (isDecreasing ? i >= finalValue : i <= finalValue); j++, i+=increment )
            {
                Experiment clonedExperiment = (Experiment) experiment.Clone();
                string varToChangeAsString = Enum.GetName(typeof (Experiment.ChangableVarEnum), variableToChange);
                string[] varToChangeStrsArray = varToChangeAsString.Split('_');
                switch(varToChangeStrsArray[0])
                {
                    case "GP": // Game paramerts property
                        Type gameParamsType = typeof (GameParameters);
                        PropertyInfo propertyInfo = gameParamsType.GetProperty(varToChangeStrsArray[1]);
                        Type propertyType = propertyInfo.PropertyType;
                        if (propertyType == typeof(Int32))
                        {
                            propertyInfo.SetValue(clonedExperiment.ExperimentParams.GameParameters, (Int32)i, null);
                        }
                        else
                        {
                            propertyInfo.SetValue(clonedExperiment.ExperimentParams.GameParameters, i, null);
                        }

                        break;
                    case "Heur":
                        throw new NotSupportedException("Heuristics is not supported!");
                }
                Object[] paramsObj = new object[] {m_ToolStripProgressBar.ProgressBar, (int) ((j*100)/totalValues)};
                m_ExperimentList.Add(clonedExperiment);
                m_ToolStripProgressBar.ProgressBar.Invoke(new ProgressBarSetValueDelegate(ProgressBarSetValueHandler), paramsObj);
            }

        }

        private void m_RunExperimentsButton_Click(object sender, EventArgs e)
        {
            ThreadStart threadStart = RunExperiments;
            Thread experimentsThread = new Thread(threadStart);
            experimentsThread.IsBackground = true;
            experimentsThread.Name = "ExperimentsThread";
            m_RunExperimentsButton.Enabled = false;
            m_AddToListButton.Enabled = false;
            m_TotalProgress.Value = 0;
            m_CurrentProgress.Value = 0;
            experimentsThread.Start();
        }

        private void RunExperiments()
        {
            ExperimentRunner experimentRunner = new ExperimentRunner();
            try
            {
                experimentRunner.RunExperiments(m_ExperimentList);
            }
            catch (Exception e)
            {
                string error = String.Format(
                    "Error creating the complex experiment\nCause:{0}\n\n{1}",
                    e.Message, e.StackTrace);
                //TODO: Debug
                //MessageBox.Show(error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            Object[] paramsObj = new object[] { m_AddToListButton, true };
            m_AddToListButton.Invoke(new SetContorlStatusDelegate(SetControlStatusHandler), paramsObj);
            paramsObj = new object[] { m_RunExperimentsButton, true };
            m_RunExperimentsButton.Invoke(new SetContorlStatusDelegate(SetControlStatusHandler), paramsObj);
        }

        private void PercentageCompleteCurExp(double percentage)
        {
            Object[] paramsObj = new object[] { m_CurrentProgress, (int)Math.Round(percentage)};
            m_CurrentProgress.Invoke(new ProgressBarSetValueDelegate(ProgressBarSetValueHandler), paramsObj);
        }

        private void PercentageCompleteTotalExp(double percentage)
        {
            Object[] paramsObj = new object[] { m_TotalProgress, (int)Math.Round(percentage) };
            m_TotalProgress.Invoke(new ProgressBarSetValueDelegate(ProgressBarSetValueHandler), paramsObj);
        }

        #region /// Menu Bar Event Handlers ///


        private void clearAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            m_ExperimentList.Clear();
        }

        private void removeSelectedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            HashSet<int> idList = new HashSet<int>();
            foreach (DataGridViewRow row in m_ExperimentsGrid.SelectedRows)
            {
                int? id = row.Cells[0].Value as int?;
                if (id.HasValue)
                {
                    idList.Add(id.Value);
                }
            }
            List<Experiment> experimentsToRemove = new List<Experiment>();
            foreach (Experiment experiment in m_ExperimentList)
            {
                if (idList.Contains(experiment.ExperimentParams.ExperimentId))
                {
                    experimentsToRemove.Add(experiment);
                }
            }
            m_ExperimentList.RemoveAll(experimentsToRemove);
        }
        

        #endregion

        private void geneticRunner_Click(object sender, EventArgs e)
        {
            GeneticAlgorithm ga = new GeneticAlgorithm();
            ga.doGeneticAlgorithm();
        }
    }
}